package com.jgp.sys.query.jpa;

import com.jgp.sys.query.*;
import com.jgp.common.utils.ReflectUtil;
import com.jgp.common.persistence.Model;
import org.springframework.data.domain.Sort;

import javax.persistence.criteria.*;
import javax.persistence.criteria.Order;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * 项目   jgp-cloud-parent
 * 作者   loufei
 * 时间   2018/5/28
 */
public class CriteriaExpression {

    private static final boolean DEFAULT_IGNORE_CASE = true;
    /**
     * 是否忽略大小写
     */
    private static Boolean ignoreCase = DEFAULT_IGNORE_CASE;

    public static Predicate[] toPredicate(QueryFilterList filters, Root root, CriteriaBuilder cb){

        List<Predicate> ands = new ArrayList<>();
        for (QueryFilter filter : filters) {
            /*if (!(Objects.nonNull(filter.getValue()) || Operator.valueOf(filter.getOperator()) == Operator
                    .isn || Operator.valueOf(filter.getOperator()) == Operator.isnn))
                continue;*/
            Class javaType = ReflectUtil.getDeclaredFieldType(root.getJavaType(), filter.getName());
            ands.add(toPredicate(root,cb, javaType, filter.getName(),filter.getOperator(), filter.getValue()));
        }

        return ands.toArray(new Predicate[ands.size()]);
    }

    public static <M extends Model> List<Order> toOrder(OrderList orders, Root<M> root, CriteriaBuilder cb) {
        List<Order> orderList = new ArrayList<>();
        for (com.jgp.sys.query.Order order : orders) {
            switch (order.getDirection()){
                case ASC:
                    orderList.add(cb.asc(root.get(order.getField())));
                    break;
                case DESC:
                    orderList.add(cb.desc(root.get(order.getField())));
                    break;
            }
        }
        return orderList;
    }

    public static Sort toSort(OrderList orders) {
        List<String> asc = new ArrayList<>();
        List<String> desc = new ArrayList<>();
        if(Objects.isNull(orders)||orders.size()==0) return null;
        for (com.jgp.sys.query.Order order : orders) {
            if(order.getDirection().equals(OrderDirection.ASC)){
                asc.add(order.getField());
            }else if(order.getDirection().equals(OrderDirection.DESC)){
                desc.add(order.getField());
            }
        }
        Sort sort = null;
        if(asc.size()>0){
            sort = new Sort(Sort.Direction.ASC,asc.toArray(new String[asc.size()]));
        }
        if(desc.size()>0){
            Sort s = new Sort(Sort.Direction.DESC,desc.toArray(new String[desc.size()]));
            if(Objects.nonNull(sort)){
                sort.and(s);
            }else{
                sort = s;
            }
        }

        return sort;
    }

    public static Predicate toPredicate(Root root, CriteriaBuilder cb, Class clazz, String name, Operator
            operator, Object value) {
        Predicate predicate = null;
        if (Objects.isNull(clazz)) return null;
        if (CharSequence.class.isAssignableFrom(clazz)) {
            Expression<String> stringPath = root.get(name).as(String.class);
            switch (operator) {
                case eq:
                    predicate = cb.equal(stringPath, value);
                    break;
                case ne:
                    predicate = cb.notEqual(stringPath, value);
                    break;
                case gt:
                    predicate = cb.greaterThan(stringPath, (String) value);
                    break;
                case lt:
                    predicate = cb.lessThan(stringPath, (String) value);
                    break;
                case ge:
                    predicate = cb.greaterThanOrEqualTo(stringPath, (String) value);
                    break;
                case le:
                    predicate = cb.lessThanOrEqualTo(stringPath, (String) value);
                    break;
                case like:
                    String like = "%" + value + "%";
                    predicate = ignoreCase ? cb.like(cb.lower(stringPath), like.toLowerCase()) : cb.like
                            (stringPath, like);
                    break;
                case likeL:
                    String likeL = value + "%";
                    predicate = ignoreCase ? cb.like(cb.lower(stringPath), likeL.toLowerCase()) : cb.like
                            (stringPath, likeL);
                    break;
                case likeR:
                    String likeR = "%" + value;
                    predicate = ignoreCase ? cb.like(cb.lower(stringPath), likeR.toLowerCase()) : cb.like
                            (stringPath, likeR);
                    break;
                case in:
                    predicate = stringPath.in((Collection<?>) value);
                    break;
                case nin:
                    predicate = cb.not(stringPath.in((Collection<?>) value));
                    break;
                case isn:
                    predicate = cb.isNull(stringPath);
                    break;
                case isnn:
                    predicate = cb.isNotNull(stringPath);
                    break;
            }
        } else if (Number.class.isAssignableFrom(clazz)) {
            Expression numberPath = root.get(name).as(clazz);
            boolean flag = false;
            switch (operator) {
                case eq:
                    predicate = cb.equal(numberPath, value);
                    flag = true;
                    break;
                case ne:
                    predicate = cb.notEqual(numberPath, value);
                    flag = true;
                    break;
                case in:
                    predicate = numberPath.in((Collection<?>) value);
                    flag = true;
                    break;
                case nin:
                    predicate = cb.not(numberPath.in(((Collection<?>) value)));
                    flag = true;
                    break;
                case isn:
                    predicate = cb.isNull(numberPath);
                    flag = true;
                    break;
                case isnn:
                    predicate = cb.isNotNull(numberPath);
                    flag = true;
                    break;
            }
            if (!flag) {
                if (Integer.class.isAssignableFrom(clazz)) {
                    Expression path = root.get(name).as(Integer.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (Integer) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (Integer) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (Integer) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (Integer) value);
                            break;
                        case between:
                            NumberRange<Integer> numberRange = (NumberRange<Integer>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }else if(Long.class.isAssignableFrom(clazz)){
                    Expression path = root.get(name).as(Long.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (Long) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (Long) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (Long) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (Long) value);
                            break;
                        case between:
                            NumberRange<Long> numberRange = (NumberRange<Long>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }else if(Float.class.isAssignableFrom(clazz)){
                    Expression path = root.get(name).as(Float.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (Float) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (Float) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (Float) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (Float) value);
                            break;
                        case between:
                            NumberRange<Float> numberRange = (NumberRange<Float>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }else if(Double.class.isAssignableFrom(clazz)){
                    Expression path = root.get(name).as(Double.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (Double) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (Double) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (Double) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (Double) value);
                            break;
                        case between:
                            NumberRange<Double> numberRange = (NumberRange<Double>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }else if(Short.class.isAssignableFrom(clazz)){
                    Expression path = root.get(name).as(Short.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (Short) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (Short) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (Short) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (Short) value);
                            break;
                        case between:
                            NumberRange<Short> numberRange = (NumberRange<Short>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }else if(BigDecimal.class.isAssignableFrom(clazz)){
                    Expression path = root.get(name).as(BigDecimal.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (BigDecimal) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (BigDecimal) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (BigDecimal) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (BigDecimal) value);
                            break;
                        case between:
                            NumberRange<BigDecimal> numberRange = (NumberRange<BigDecimal>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }else if(BigInteger.class.isAssignableFrom(clazz)){
                    Expression path = root.get(name).as(BigInteger.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (BigInteger) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (BigInteger) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (BigInteger) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (BigInteger) value);
                            break;
                        case between:
                            NumberRange<BigInteger> numberRange = (NumberRange<BigInteger>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }else if(Byte.class.isAssignableFrom(clazz)){
                    Expression path = root.get(name).as(Byte.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (Byte) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (Byte) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (Byte) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (Byte) value);
                            break;
                        case between:
                            NumberRange<Byte> numberRange = (NumberRange<Byte>) value;
                            predicate = cb.between(path, numberRange.getFrom(), numberRange.getTo());
                            break;
                    }
                }
            }

        } else if (Boolean.class.isAssignableFrom(clazz)) {
            Expression path = root.get(name).as(Boolean.class);
            switch (operator) {
                case eq:
                    predicate = cb.equal(path, value);
                    break;
                case ne:
                    predicate = cb.notEqual(path, value);
                    break;
                case isn:
                    predicate = path.isNull();
                    break;
                case isnn:
                    predicate = path.isNotNull();
                    break;
            }
        } else if (Date.class.isAssignableFrom(clazz) || LocalDate.class.isAssignableFrom(clazz) ||
                LocalDateTime.class.isAssignableFrom(clazz) || LocalTime.class.isAssignableFrom(clazz)) {
            Expression dataPath = root.get(name);
            boolean flag = false;
            switch (operator) {
                case eq:
                    predicate = cb.equal(dataPath, value);
                    flag = true;
                    break;
                case ne:
                    predicate = cb.notEqual(dataPath, value);
                    flag = true;
                    break;
                case in:
                    predicate = dataPath.in((Collection<?>) value);
                    flag = true;
                    break;
                case nin:
                    predicate = cb.not(dataPath.in((Collection<?>) value));
                    flag = true;
                    break;
                case isn:
                    predicate = cb.isNull(dataPath);
                    flag = true;
                    break;
                case isnn:
                    predicate = cb.isNotNull(dataPath);
                    flag = true;
                    break;
            }

            if (!flag) {
                if (Date.class.isAssignableFrom(clazz)) {
                    Expression path = root.get(name).as(Date.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (Date) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (Date) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (Date) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (Date) value);
                            break;
                        case between:
                            DateRange<Date> dateRange = (DateRange<Date>) value;
                            predicate = cb.between(path, dateRange.getFrom(), dateRange.getTo());
                            break;
                    }
                } else if (LocalDate.class.isAssignableFrom(clazz)) {
                    Expression path = root.get(name).as(LocalDate.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (LocalDate) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (LocalDate) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (LocalDate) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (LocalDate) value);
                            break;
                        case between:
                            DateRange<LocalDate> dateRange = (DateRange<LocalDate>) value;
                            predicate = cb.between(path, dateRange.getFrom(), dateRange.getTo());
                            break;
                    }

                } else if (LocalDateTime.class.isAssignableFrom(clazz)) {
                    Expression path = root.get(name).as(LocalDateTime.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (LocalDateTime) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (LocalDateTime) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (LocalDateTime) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (LocalDateTime) value);
                            break;
                        case between:
                            DateRange<LocalDateTime> dateRange = (DateRange<LocalDateTime>) value;
                            predicate = cb.between(path, dateRange.getFrom(), dateRange.getTo());
                            break;
                    }
                } else if (LocalTime.class.isAssignableFrom(clazz)) {
                    Expression path = root.get(name).as(LocalTime.class);
                    switch (operator) {
                        case gt:
                            predicate = cb.greaterThan(path, (LocalTime) value);
                            break;
                        case lt:
                            predicate = cb.lessThan(path, (LocalTime) value);
                            break;
                        case ge:
                            predicate = cb.greaterThanOrEqualTo(path, (LocalTime) value);
                            break;
                        case le:
                            predicate = cb.lessThanOrEqualTo(path, (LocalTime) value);
                            break;
                        case between:
                            DateRange<LocalTime> dateRange = (DateRange<LocalTime>) value;
                            predicate = cb.between(path, dateRange.getFrom(), dateRange.getTo());
                            break;
                    }
                }
            }

        } else if (Enum.class.isAssignableFrom(clazz)) {
            Expression path = root.get(name);
            switch (operator) {
                case eq:
                    predicate = cb.equal(path, value);
                    break;
                case ne:
                    predicate = cb.notEqual(path, value);
                    break;
                case gt:
                    predicate = cb.greaterThan(path, (Enum)value);
                    break;
                case lt:
                    predicate = cb.lessThan(path, (Enum)value);
                    break;
                case ge:
                    predicate = cb.greaterThanOrEqualTo(path, (Enum)value);
                    break;
                case le:
                    predicate = cb.lessThanOrEqualTo(path, (Enum)value);
                    break;
                case in:
                    predicate = path.in((Collection<?>) value);
                    break;
                case nin:
                    predicate = cb.not(path.in((Collection<?>) value));
                    break;
                case isn:
                    predicate = cb.isNull(path);
                    break;
                case isnn:
                    predicate = cb.isNotNull(path);
                    break;
            }
        } else {
            Expression path = root.get(name);
            switch (operator) {
                case eq:
                    predicate = cb.equal(path, value);
                    break;
                case ne:
                    predicate = cb.notEqual(path, value);
                    break;
                case in:
                    predicate = path.in((Collection<?>) value);
                    break;
                case nin:
                    predicate = cb.not(path.in((Collection<?>) value));
                    break;
                case isn:
                    predicate = cb.isNull(path);
                    break;
                case isnn:
                    predicate = cb.isNotNull(path);
                    break;
            }
        }
        return predicate;
    }

}
